﻿Imports MPQService
Imports System.ServiceModel

<ServiceBehavior(ConcurrencyMode:=ConcurrencyMode.Reentrant, InstanceContextMode:=InstanceContextMode.Single)> _
Public Class Game

    Dim Server As GameServer
    Dim _Name As String
    Public Property Name As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property

    Dim _GameID As Integer

    Public Sub New(ByVal Name As String, ByVal GameID As Integer, ByRef GameServer As GameServer)
        _Name = Name
        Server = GameServer
        _GameID = GameID
        If GameServer.SingleGameOnly Then _HaveHost = True
        StatusHandler = New StatusHandler(AddressOf _UpdateStatus, Server)
    End Sub

    Event UpdateGameStatus(ByVal ID As Integer, ByVal Status As String, ByVal Detail As String)
    Event UpdateGameScores(ByVal ID As Integer, ByVal Scores As SortedList(Of String, Long))
    Event EndGame(ByVal ID As Integer)
    Event GameReset(ByVal ID As Integer)
    Event SendQuestion(ByVal ID As Integer, ByVal Question As Question)
    Event Start(ByVal ID As Integer)
    Event EndQuestion(ByVal ID As Integer, ByVal CorrectAnswer As String, ByVal Explanation As String)

    Public _HaveHost As Boolean = False
    Dim Playing As Boolean = False
    Friend Users As New List(Of User)
    Public Clients As New List(Of IClient)
    Dim UsedIDs As New List(Of Integer)
    Dim Questions As New Database
    Dim RemainingQuestions As New List(Of Integer)
    Dim CurrentQuestionNo As Integer
    Friend CurrentQuestion As Database.QuestionsRow
    Dim RemainingUsers As New List(Of User)
    Dim StartingNextQuestion As Boolean = False
    Friend Scores As New SortedList(Of String, Long)

    Dim UpdatingScores As Boolean = False
    Sub UpdateScores()
        If UpdatingScores = True Then Exit Sub
        UpdatingScores = True
        Try
            Scores.Clear()
            For Each User As User In Users
                Try
                    Scores.Add(User.Name, User.Score)
                Catch
                    Dim i As Integer = 2
                    Do
                        Try
                            Scores.Add(User.Name & "(" & i & ")", User.Score)
                            Exit Do
                        Catch
                        End Try
                    Loop
                End Try
            Next
            RaiseEvent UpdateGameScores(_GameID, Scores)
            For Each User As User In Users
                If User.Channel.State = CommunicationState.Opened Then
                    User.CallBack.UpdateGameScores(Scores)
                Else
                    Throw New Exception("Channel Unavailable")
                End If
            Next
        Catch ex As Exception
            UpdateStatus("Error updating scores:" & ex.Message, "")
        End Try
        UpdatingScores = False
    End Sub

    Public Sub RegisterClient(ByVal Callback As IClient)
        Try
            Clients.Add(Callback)
            Callback.OnRegisterClient(_Name)
            Callback.HaveHost(_HaveHost)
        Catch ex As Exception
            Callback.OnFailRegisterClient(ex.Message)
        End Try
    End Sub

    Public Sub GotHost(ByVal GotHost As Boolean)
        _HaveHost = GotHost
        Dim i As Integer = 0
        Try
sendhavehost: For Each Client As IClient In Clients
                Client.HaveHost(GotHost)
            Next
        Catch
            i += 1
            If i < 5 Then
                GoTo sendhavehost
            End If
        End Try
    End Sub

    Public Sub HostGame(ByVal QuizFile As String)
        Dim HGT As New Threading.Thread(AddressOf _HostGame)
        HGT.Name = "Host Game Thread"
        HGT.Start(QuizFile)
    End Sub

    Public Sub _HostGame(ByVal QuizFile As String)
        Questions.Clear()
        Dim stream As New IO.FileStream("Quiz Files\" & QuizFile, IO.FileMode.Open)
        Dim comp As New System.IO.Compression.GZipStream(stream, IO.Compression.CompressionMode.Decompress)

        Dim RXT As New Threading.Thread(AddressOf _ReadXML)
        RXT.Name = "Read XML Thread"
        RXT.Start(comp)

        UpdateStatus("Loading " & QuizFile, "0%")
        Dim LastPercent As Integer
        Do While RXT.IsAlive
            If Not LastPercent = CInt(stream.Position * 100 / stream.Length) Then
                LastPercent = CInt(stream.Position * 100 / stream.Length)
                UpdateStatus("Loading " & QuizFile, LastPercent & "%")
            End If
        Loop

        comp.Close()
        stream.Close()
        UpdateScores()

        UpdateStatus("Loading " & QuizFile, "Complete")
    End Sub

    Sub _ReadXML(ByVal Stream As IO.Stream)
        Questions.ReadXml(Stream)
    End Sub

    Public Sub RegisterHost(ByVal Name As String, ByVal QuizFile As String, ByVal Callback As IClient)
        GotHost(True)
newrandom: Dim ID As Integer = (New Random).Next()
        If UsedIDs.Contains(ID) Then GoTo newrandom
        UsedIDs.Add(ID)
        Dim Results As EnumerableRowCollection(Of ServerData.UsersRow) = From Users In Server.ServerData.Users Where Users.Username = Name
        Dim User As New User(Callback, OperationContext.Current.Channel, Name, ID, True, Results(0).TimeMultiplier)
        Users.Add(User)
        UpdateStatus("Registered " & Name, "")
        HostGame(QuizFile)
        Callback.OnRegisterHost(User.ID)
    End Sub

    Public Sub RegisterUser(ByVal Name As String, ByVal Callback As IClient)
newrandom: Dim ID As Integer = (New Random).Next()
        If UsedIDs.Contains(ID) Or ID = 0 Then GoTo newrandom
        UsedIDs.Add(ID)
        Dim Results As EnumerableRowCollection(Of ServerData.UsersRow) = From Users In Server.ServerData.Users Where Users.Username = Name
        Dim User As New User(Callback, OperationContext.Current.Channel, Name, ID, False, Results(0).TimeMultiplier)
        Users.Add(User)
        UpdateStatus("Registered " & Name, "")
        User.CallBack.OnRegisterUser(User.ID)
        UpdateScores()
    End Sub

    Public Sub UnregisterUser(ByVal ID As Integer, ByVal Callback As IClient)
        Dim URUT As New Threading.Thread(AddressOf _UnregisterUser)
        URUT.Name = "UnregisterUserThread"
        URUT.Start(ID)
        Dim URCT As New Threading.Thread(AddressOf _UnregisterClient)
        URCT.Name = "UnregisterClientThread"
        URCT.Start(Callback)
    End Sub

    Sub _UnregisterUser(ByVal ID As Integer)
        Dim User As User = FindUserByID(ID)
        If User Is Nothing Then Exit Sub
        Try
            UpdateStatus(User.Name & " has left the game", "")
            Users.Remove(User)
            UsedIDs.Remove(ID)
            If User.IsHost = True Then
                GotHost(False)
                If Playing = False Then Questions = New Database
            End If
            If RemainingUsers.Contains(User) Then
                RemainingUsers.Remove(User)
            End If
        Catch ex As Exception
            UpdateStatus("Error removing user: " & User.Name, "")
            UpdateStatus(ex.Message, "")
        End Try
    End Sub

    Public Sub _UnregisterClient(ByVal Callback As IClient)
        Try
            Clients.Remove(Callback)
            If Clients.Count = 0 Then
                RaiseEvent EndGame(_GameID)
                Server.Games.Remove(_GameID)
                Server.GameNames.Remove(Name)
                UpdateStatus("Game Closed", "")
                System.Threading.Thread.Sleep(5000)
            End If
        Catch ex As Exception
            UpdateStatus("Error removing client:", "")
            UpdateStatus(ex.Message, "")
        End Try
    End Sub

    Public Sub UnregisterClient(ByVal Callback As IClient)
        Dim URCT As New Threading.Thread(AddressOf _UnregisterClient)
        URCT.Name = "UnregisterClientThread"
        URCT.Start(Callback)
    End Sub

    Public Sub GetGameDetails(ByVal Callback As IClient)
        If Questions.QuizInfo.Count = 0 Then Exit Sub
        With CType(Questions.QuizInfo.Rows(0), Database.QuizInfoRow)
            Dim Details As New GameDetails()
            If Not .IsQuizImageNull Then
                Details.Image = .QuizImage
            End If
            If Not .IsQuizNameNull Then
                Details.Name = .QuizName
            End If
            If Not .IsQuizDescripitonNull Then
                Details.Description = .QuizDescripiton
            End If
            If Not .IsQuizAuthorNull Then
                Details.Author = .QuizAuthor
            End If
            Details.TotalQuestions = Questions.Questions.Count
            Callback.OnGetGameDetails(Details)
        End With
    End Sub

    Public Sub RecieveAnswer(ByVal Answer As Answer, ByVal ID As Integer)
        Try
            Dim User As User = FindUserByID(ID)
            RemainingUsers.Remove(User)
            UpdateStatus(User.Name & " has answered", "")
            If User.Channel.State = CommunicationState.Opened Then
                Dim CorrectAnswers As List(Of String) = ParseAnswers(CurrentQuestion.Correct)
                If CorrectAnswers.Contains(Answer.ToString) Then
                    User.Score += 1
                    Try
                        User.CallBack.OnCorrectAnswer(CurrentQuestion.Explanation)
                    Catch
                        User.CallBack.OnCorrectAnswer("")
                    End Try
                Else
                    Select Case CurrentQuestion.Correct
                        Case "A"
                            Try
                                User.CallBack.OnIncorrectAnswer(CurrentQuestion.A, CurrentQuestion.Explanation)
                            Catch
                                User.CallBack.OnIncorrectAnswer(CurrentQuestion.A, "")
                            End Try
                        Case "B"
                            Try
                                User.CallBack.OnIncorrectAnswer(CurrentQuestion.B, CurrentQuestion.Explanation)
                            Catch
                                User.CallBack.OnIncorrectAnswer(CurrentQuestion.B, "")
                            End Try
                        Case "C"
                            Try
                                User.CallBack.OnIncorrectAnswer(CurrentQuestion.C, CurrentQuestion.Explanation)
                            Catch
                                User.CallBack.OnIncorrectAnswer(CurrentQuestion.C, "")
                            End Try
                        Case "D"
                            Try
                                User.CallBack.OnIncorrectAnswer(CurrentQuestion.D, CurrentQuestion.Explanation)
                            Catch
                                User.CallBack.OnIncorrectAnswer(CurrentQuestion.D, "")
                            End Try
                    End Select
                End If
            Else
                Throw New Exception("Channel Unavailable")
            End If
            UpdateScores()
        Catch ex As Exception
            UpdateStatus("Error recieving answer: " & ex.Message, "")
        End Try
    End Sub

    Public Sub RecieveAnswer(ByVal Answer As String, ByVal ID As Integer)
        Try
            Dim User As User = FindUserByID(ID)
            RemainingUsers.Remove(User)
            UpdateStatus(User.Name & " has answered", "")
            Dim CorrectAnswers As List(Of String) = ParseAnswers(CurrentQuestion.Correct)
            If CorrectAnswers.Contains(Answer.ToUpper) Then
                User.Score += 1
                Try
                    User.CallBack.OnCorrectAnswer(CurrentQuestion.Explanation)
                Catch
                    User.CallBack.OnCorrectAnswer("")
                End Try
            Else
                Try
                    User.CallBack.OnIncorrectAnswer(CorrectAnswers(0), CurrentQuestion.Explanation)
                Catch
                    User.CallBack.OnIncorrectAnswer(CorrectAnswers(0), "")
                End Try
            End If
            UpdateScores()
        Catch ex As Exception
            UpdateStatus("Error recieving answer: " & ex.Message, "")
        End Try
    End Sub

    Function ParseAnswers(ByVal AnswerString As String) As List(Of String)
        Dim Answers As New List(Of String)
        Dim CurrentAnswer As String = ""
        For i As Integer = 0 To AnswerString.Length - 1
            If AnswerString(i) = ";" Then
                Answers.Add(CurrentAnswer.ToUpper)
                CurrentAnswer = ""
                If AnswerString(i + 1) = " " Then
                    i += 1
                End If
            Else
                CurrentAnswer += AnswerString(i)
            End If
        Next
        If Not CurrentAnswer = "" Then
            Answers.Add(CurrentAnswer.ToUpper)
        End If
        Return Answers
    End Function

    Public Sub OutOfTime(ByVal ID As Integer)
        Try
            Dim User As User = FindUserByID(ID)
            RemainingUsers.Remove(User)
            UpdateStatus(User.Name & " ran out of time", "")
            Try
                User.CallBack.OnIncorrectAnswer(CurrentQuestion.Correct, CurrentQuestion.Explanation)
            Catch
                User.CallBack.OnIncorrectAnswer(CurrentQuestion.Correct, "")
            End Try
            UpdateScores()
        Catch ex As Exception
            UpdateStatus("Error handling out of time rule: " & ex.Message, "")
        End Try
    End Sub

    Dim UserCheckTimer As Threading.Timer
    Dim WaitingForShown As Boolean = False
    Sub CheckUsers()
        Try
            If RemainingUsers.Count = 0 And StartingNextQuestion = False Then
                UserCheckTimer = Nothing
                StartingNextQuestion = True
                Try
                    RaiseEvent EndQuestion(_GameID, CurrentQuestion.Correct, CurrentQuestion.Explanation)
                Catch
                    RaiseEvent EndQuestion(_GameID, CurrentQuestion.Correct, "")
                End Try
                NextQuestion()
            ElseIf RemainingUsers.Count = 1 Then
                If Not WaitingForShown Then UpdateStatus("Waiting for " & RemainingUsers(0).Name, "")
                WaitingForShown = True
            Else
                WaitingForShown = False
            End If
        Catch ex As Exception
            UpdateStatus("Error recieving answer: " & ex.Message, "")
        End Try
    End Sub

    Function FindUserByID(ByVal ID As Integer) As User
        For Each User As User In Users
            If User.ID = ID Then
                Return User
            End If
        Next
        Return Nothing
    End Function

    Public Sub StartQuiz()
        Try
            Playing = True
            For i = 0 To Questions.Questions.Rows.Count - 1
                RemainingQuestions.Add(i)
            Next
            UpdateStatus("Quiz Starts", "")
            NextQuestion()
        Catch ex As Exception
            UpdateStatus("Error starting Quiz: " & ex.Message, "")
        End Try
    End Sub

    Public Sub ReadyToStart(ByVal ID As Integer)
        UsersReadyToStart.Add(FindUserByID(ID))
    End Sub
    Dim UsersReadyToStart As New List(Of User)

    Public Sub NextQuestion()
        Dim NQT As New Threading.Thread(AddressOf _NextQuestion)
        NQT.Name = "NewQuestionThread"
        NQT.Start()
    End Sub

    Public Sub _NextQuestion()
        Try
            UpdateScores()
            If RemainingQuestions.Count = 0 Then
                EndOfQuiz()
                Exit Sub
            End If
            System.Threading.Thread.Sleep(3000)
nextquestion: If RemainingQuestions.Count = 0 Then
                EndOfQuiz()
                Exit Sub
            End If
            CurrentQuestionNo = RemainingQuestions((New Random).Next(0, RemainingQuestions.Count - 1))
            UpdateStatus("----------", "")
            UpdateStatus("Loading question " & CurrentQuestionNo + 1, "Preparing Question")
            UpdateStatus("Questions remaining: " & RemainingQuestions.Count - 1, "")
            Dim Question As New Question
            Try
                CurrentQuestion = Questions.Questions.Rows(CurrentQuestionNo)
                Question.QuestionID = CurrentQuestion.QuestionID
                Question.QuestionText = CurrentQuestion.QuestionText
                Question.CorrectAnswer = CurrentQuestion.Correct
                Try
                    If CurrentQuestion.IsMultichoice = False Then
                        Question.IsMultichoice = False
                    Else
                        Question.A = CurrentQuestion.A
                        Question.B = CurrentQuestion.B
                        Question.C = CurrentQuestion.C
                        Question.D = CurrentQuestion.D
                        Question.IsMultichoice = True
                    End If
                Catch
                    If CurrentQuestion.IsInput = True Then
                        Question.IsMultichoice = False
                    Else
                        Question.A = CurrentQuestion.A
                        Question.B = CurrentQuestion.B
                        Question.C = CurrentQuestion.C
                        Question.D = CurrentQuestion.D
                        Question.IsMultichoice = True
                    End If
                End Try
            Catch ex As Exception
                UpdateStatus("Error loading question:" & ex.Message, "")
                RemainingQuestions.Remove(CurrentQuestionNo)
                GoTo nextquestion
            End Try
            UpdateStatus("Loading question " & CurrentQuestionNo + 1, "Preparing Image")
            Try
                'Dim MS As New IO.MemoryStream(Convert.FromBase64String(CurrentQuestion.Image))
                Question.Image = CurrentQuestion.Image
                If CurrentQuestion.Image.Length >= 6553600 Then
                    UpdateStatus("Image too large to send", "")
                    Question.Image = Nothing
                End If
            Catch
                Question.Image = Nothing
            End Try

            Dim Resends As Integer = 0
restartsend: Try
                For Each User As User In Users
                    UpdateStatus("Loading question " & CurrentQuestionNo + 1, "Sending to " & User.Name)
                    Question.TimeLimit = CurrentQuestion.TimeLimit * User.TimeMultiplier
resend:             Try
                        If User.Channel.State = CommunicationState.Opened Then
                            User.CallBack.SendQuestion(Question)
                        Else
                            Throw New Exception("Channel Unavailable")
                        End If
                    Catch ex As Exception
                        UpdateStatus("Error sending to " & User.Name & ": " & ex.Message, "")
                        Resends += 1
                        If Resends < 5 Then GoTo resend
                        Resends = 0
                    End Try
                Next
                RaiseEvent SendQuestion(_GameID, Question)
            Catch
                GoTo restartsend
            End Try

addusers:   Try
                For Each User As User In Users
                    RemainingUsers.Add(User)
                Next
            Catch
                RemainingUsers.Clear()
                GoTo addusers
            End Try

            UpdateStatus("Loading question " & CurrentQuestionNo + 1, "Complete")

            Dim StartThread As New Threading.Thread(AddressOf _Start)
            StartThread.Start()

        Catch ex As Exception
            UpdateStatus("Error processing next question: " & ex.Message, "")
        End Try
    End Sub

    Sub _Start()
        Do Until UsersReadyToStart.Count >= Users.Count
            System.Threading.Thread.Sleep(500)
        Loop

start:  Try
            For Each User As User In Users
                User.CallBack.Start()
            Next
        Catch
        End Try
        RaiseEvent Start(_GameID)

        StartingNextQuestion = False
        RemainingQuestions.Remove(CurrentQuestionNo)
        UsersReadyToStart.Clear()
        UserCheckTimer = New Threading.Timer(New Threading.TimerCallback(AddressOf CheckUsers), Nothing, 1000, 1000)
    End Sub

    Sub EndOfQuiz()
        Dim EOQT As New Threading.Thread(AddressOf _EndOfQuiz)
        EOQT.Name = "EndOfQuizThread"
        EOQT.Start()
    End Sub

    Sub _EndOfQuiz()
        Try
            UpdateStatus("Quiz Ends", "")
            Threading.Thread.Sleep(3000)
            For Each User As User In Users
                User.CallBack.EndOfQuiz(User.Score, Questions.Questions.Count)
                If Not Server.SingleGameOnly Then
                    Dim Username As String = User.Name
                    Dim _User As EnumerableRowCollection(Of ServerData.UsersRow) = From Users In Server.ServerData.Users Where Users.Username = Username
                    _User(0).Score = (_User(0).Score + (User.Score / Questions.Questions.Count) * 100) / 2
                    If _User(0).IsTeamNull = False Then
                        _User(0).TeamsRow.Score = (_User(0).TeamsRow.Score + ((User.Score / User.TimeMultiplier) / Questions.Questions.Count) * 100) / 2
                    End If
                End If
            Next
            GotHost(False)
            Playing = False
            Questions = New Database
            Users.Clear()
            UpdateStatus("", "")
            UpdateStatus("----------", "")
            UpdateStatus("", "")
            UpdateStatus("Game Reset", "")
            RaiseEvent GameReset(_GameID)
        Catch ex As Exception
            UpdateStatus("Error handling end of game: " & ex.Message, "")
        End Try
    End Sub

    Dim StatusHandler As StatusHandler
    Sub UpdateStatus(ByVal Status As String, ByVal Detail As String)
        StatusHandler.AddStatus(Status, Detail)
    End Sub

    Sub _UpdateStatus(ByVal Status As String, ByVal Detail As String)
        Try
            RaiseEvent UpdateGameStatus(_GameID, Status, Detail)

            For Each Client As IClient In Clients
                Client.SendStatus(Status, Detail)
            Next
        Catch
        End Try
    End Sub

    Public Sub GetHaveHost(ByVal Callback As IClient)
        Callback.HaveHost(_HaveHost)
    End Sub
End Class

Class StatusHandler
    Dim Statuses As New Collections.Queue
    Dim Server As GameServer

    Dim _AddStatus As Action(Of String, String)

    Public Sub AddStatus(ByVal Status As String, ByVal Detail As String)
        Statuses.Enqueue(New Status With {.Status = Status, .Detail = Detail})
    End Sub

    Sub New(ByVal StatusAdder As Action(Of String, String), ByVal Server As GameServer)
        Me.Server = Server
        _AddStatus = StatusAdder
        Dim ThreadPoolThread As New Threading.Thread(AddressOf StatusManager)
        ThreadPoolThread.Start()
    End Sub

    Private Sub StatusManager()
        Do Until Server.Closing = True
            Do Until Statuses.Count = 0
                Dim Status As Status = Statuses.Dequeue
                _AddStatus.Invoke(Status.Status, Status.Detail)
            Loop
            System.Threading.Thread.Sleep(100)
        Loop
    End Sub

    Structure Status
        Dim Status As String
        Dim Detail As String
    End Structure
End Class